🧪 QA · Version 2.0

Subscribe Web · Test Scenarios
+ Playwright Automation Guide

Comprehensive test documentation for ConstructQ Subscribe Web · 6-step subscription wizard · 14 test areas · 72 test cases · Playwright automation + payment/OTP/trial flows
📅 วันที่: 13 ก.ค. 2569 (2026-07-13) 👤 Owner: วุธ (PM/SA · QA Lead) 🎯 Audience: QA · Dev · Automation 📄 Source: ConstructQ_Subscribe.html
14
Test Areas
72
Test Cases
6
Wizard Steps
4·3
Packages · Pay Methods

01บทนำ · About This Guide

Test scope · deliverables · references

เอกสารนี้เป็น Test Documentation สำหรับหน้าจอ Subscribe Web v2.0 ครอบคลุม:

  • Test Scenarios · 14 test areas — ครอบคลุมทุก flow ของ 6-step wizard
  • Test Cases · 72 cases — Positive / Negative / Boundary / UI / Integration / A11y
  • Playwright Automation Guide — POM · fixtures · sample scripts · CI
  • Payment integration coverage — Omise · SlipOK · Bank Transfer · Trial lock
📌
Reference file: ConstructQ_Subscribe.html · React SPA · standalone (no AppShell) · 6-step wizard · bilingual TH/EN · font size toggle · 4 packages (Trial/Starter/Pro/Enterprise) · 3 payment methods

02Screen Overview

6-step wizard · 4 packages · 3 payment methods · TH/EN · font size

🗺️ Wizard Structure

StepTitle (TH)Title (EN)Key Elements
01ข้อกำหนดและเงื่อนไขTerms & ConditionsAccordion (11 sections) · 2 checkboxes (T&C + PDPA)
02เลือกแพ็คเก็จChoose Package4 plan cards (Trial/Starter/Pro/Enterprise) · billing cycle toggle (monthly/yearly)
03ข้อมูลบริษัทCompany DetailsCompany name · tax ID (13 digits) · address + tambon autocomplete · contact · logo upload
04ผู้ใช้งานUsersAdd user · role dropdown (6 roles) · password rules · permission matrix (view-only)
05ชำระเงินPaymentDuration stepper (months/years) · 3 tabs (Credit/QR/Bank) · summary · Trial-lock
06ขอแสดงความยินดีWelcomeSuccess message · order ref · sent-to email list · next steps

💎 4 Packages

PackageMonthlyYearlyUsersProjectsNote
Trial฿0฿051ทดลองใช้ · lock duration + payment
Starter฿3,900฿42,900103SME · save ฿3,900/yr
Professional฿7,900฿86,9003015Most popular · save ฿7,900/yr
EnterpriseCustomCustomUnlimitedUnlimitedSSO · On-prem · SLA 99.9%

💳 3 Payment Methods

  • Credit Card — via Omise gateway · 4-field form (card number, name, expiry, CVC)
  • PromptPay QR — QR code + slip upload · auto-verify via SlipOK ~30 sec
  • Bank Transfer — Kasikorn Bank info + slip upload · manual verification 1 business hour

🎨 Additional UI Features

  • Font size toggle — A- / A / A+ (13/15/17 px base)
  • Language toggle — TH 🇹🇭 / EN 🇬🇧 · updates all labels + T&C content
  • Brand mini header — ConstructQ logo · standalone page (no AppShell)
  • Vertical stepper — left side · click-to-jump navigation

03Test Strategy

Test types · priority · risk-based approach

🎯 Test Types

TypeObjectiveCoverage
PositiveHappy path · Trial + all paid plans~40%
NegativeInvalid input · payment failure · duplicate~25%
BoundaryTax ID digits · password rules · duration 1-60~10%
IntegrationOmise · SlipOK · Email · Autocomplete API~15%
UI/UXLayout · responsive · animation · font size~7%
AccessibilityKeyboard · ARIA · contrast · screen reader~3%

⚡ Priority

  • P1 — Critical · payment · save · signup completion · data integrity
  • P2 — High · validation · UX · Trial lock behavior
  • P3 — Medium · UI polish · edge cases · font size · translations

🌐 Environments

  • Dev/Staging: Omise TEST keys · SlipOK sandbox · MailHog for email capture
  • UAT: Real Omise TEST · real Kasikorn PromptPay test QR · staging DB
  • Production: Smoke only · no real charges · use test card 4242 4242 4242 4242

04Test Scenarios + Test Cases

14 areas · 72 detailed test cases

4.1 · 🏠 Landing + Navigation

TC IDScenario StepsExpected Result PriorityType
TC-01เข้าหน้า Subscribe (default TH) 1. Navigate /subscribe Standalone page · logo · TH/EN toggle · font size · Step 01 active · vertical stepper visible P1Pos
TC-02Stepper click-to-jump 1. เข้า Step 01
2. คลิก Step 03 ใน stepper
Jump ไป Step 03 · state ยัง preserved · Step 01 marked done ถ้าผ่าน · Step 02 skipped shows warning P2Pos
TC-03Back/Next buttons 1. Step 02 · กด "ถัดไป" Advance to Step 03 · scroll top · stepper updates active P1Pos
TC-04Browser back preserves state 1. Fill Step 03
2. Browser back
3. Forward
Data ยังอยู่ · form pre-filled P2Pos
TC-05Refresh page (page reload) 1. Fill Step 03
2. F5 refresh
Warning "จะสูญเสียข้อมูล" · หรือ localStorage restore P2Neg

4.2 · 📜 Step 01 · Terms & Conditions

TC IDScenario StepsExpected Result PriorityType
TC-06Accordion sections load 1. เข้า Step 01 แสดง 11 sections (นิยาม · การสมัคร · บริการ · ค่าธรรมเนียม · IP · PDPA · SLA · etc.) · ปิดหมด default P2Pos
TC-07Accordion expand/collapse 1. คลิก section 6 (ค่าธรรมเนียม) Expand animation · แสดง content · icon rotate · คลิกอีกครั้ง collapse P3UI
TC-08T&C checkbox required 1. ไม่ติ๊ก T&C
2. กด "ยอมรับและถัดไป"
Button disabled หรือ error · block advance P1Neg
TC-09PDPA checkbox required 1. ติ๊ก T&C · ไม่ติ๊ก PDPA
2. กด "ถัดไป"
Button disabled · ต้องติ๊กทั้งคู่ P1Neg
TC-10Both checked · advance 1. ติ๊กทั้ง 2 กล่อง
2. "ถัดไป"
Advance to Step 02 · state persist P1Pos
TC-11T&C EN translation 1. Toggle EN
2. Expand section
Content เปลี่ยนเป็น EN · section titles update P2Pos

4.3 · 💎 Step 02 · Choose Package

TC IDScenario StepsExpected Result PriorityType
TC-124 plan cards render 1. เข้า Step 02 4 cards (Trial · Starter · Pro · Enterprise) · Pro มี "ยอดนิยม" badge · ราคาแสดงถูก P1Pos
TC-13Cycle toggle · monthly ↔ yearly 1. คลิก "รายปี" ราคาเปลี่ยน (Starter 3,900 → 42,900) · แสดง savings ฿3,900/yr P1Pos
TC-14Trial price = 0 1. Trial monthly + yearly ฿0 ทั้งคู่ · no savings shown P2Bnd
TC-15Enterprise = ติดต่อทีมขาย 1. Click Enterprise Card shows "ติดต่อทีมขาย / Contact Sales" · no fixed price · button = "ติดต่อเรา" P1Pos
TC-16Select package · highlight 1. Click Starter card Card highlighted (border + shadow) · other cards dim · "ถัดไป" enabled P1Pos
TC-17Package bullets visible 1. View any card Bullets แสดง (5-10 items) · matches TH/EN language P2Pos
TC-18Default selection (biz rule) 1. เข้า Step 02 ครั้งแรก Pro card selected default (or no selection · confirm expected behavior) P3UI
TC-19No package selected · block advance 1. ไม่เลือก package
2. "ถัดไป"
Button disabled หรือ error prompt P1Neg

4.4 · 🏢 Step 03 · Company Details

TC IDScenario StepsExpected Result PriorityType
TC-20Required fields highlighted 1. เข้า Step 03 Company name · Tax ID · Address · Contact name · Email · Phone มี * indicator P2UI
TC-21Tax ID must be 13 digits 1. พิมพ์ "12345" Error "เลขประจำตัวผู้เสียภาษีต้อง 13 หลัก" · block save P1Neg
TC-22Tax ID valid · 13 digits 1. พิมพ์ "0123456789012" Accepted · no error · optional checksum validation P1Pos
TC-23Tax ID reject non-numeric 1. พิมพ์ "abc123" ตัวอักษรถูก strip · เหลือ "123" P2Neg
TC-24Contact email format 1. อีเมล "not-email" Error "รูปแบบอีเมลไม่ถูกต้อง" P1Neg
TC-25Contact phone format 1. Phone "02-123-4567" Accept Thai landline · หรือ mobile 08x-xxxx-xxxx P2Pos
TC-26Tambon autocomplete search 1. พิมพ์ "ห้วยขวาง" ใน tambon field Dropdown แสดง options · เลือก → auto-fill district/province/postal code P1Int
TC-27Tambon · empty state 1. พิมพ์ "xxxxx" "ไม่พบข้อมูล — ลองพิมพ์คำอื่น" P3Neg
TC-28Company logo upload 1. Upload PNG 200KB Preview shows · file size + name displayed · valid types PNG/JPG/SVG P2Pos
TC-29Logo upload · oversized 1. Upload 10 MB image Error "ไฟล์ใหญ่เกิน 5 MB" · reject P2Neg
TC-30Logo upload · invalid type 1. Upload .exe Error "รองรับเฉพาะ PNG/JPG/SVG" P2Neg

4.5 · 👥 Step 04 · Users + Roles

TC IDScenario StepsExpected Result PriorityType
TC-31Empty state · no users 1. เข้า Step 04 'ยังไม่มีผู้ใช้งาน — กด "เพิ่มผู้ใช้งาน" เพื่อเริ่ม' · CTA P3UI
TC-32Add user · valid 1. name = "สมชาย"
2. email = "somchai@t.com"
3. password = "Str0ng!Pass"
4. phone = "0812345678"
5. role = "pm"
User added · row displayed · delete icon P1Pos
TC-33Password toggle show/hide 1. Type password
2. Click 👁️
Toggle between password/text · label toggle "แสดง"/"ซ่อน" P3UI
TC-34Password rules · min 8 char 1. password = "1234" Error "อย่างน้อย 8 ตัวอักษร" P1Neg
TC-35Password rules · complexity tooltip 1. Hover password field Tooltip "ตัวพิมพ์ใหญ่ + เล็ก + ตัวเลข + สัญลักษณ์" P3UI
TC-36Role dropdown · 6 options 1. Open role dropdown 6 roles: Organize Admin · PM · QC Manager · Inspector · Fixer/Sub · Executive P2Pos
TC-37Role select · show permissions 1. Select "qc_insp" Right side matrix shows features accessible by Inspector role · view-only P2Pos
TC-38Duplicate email · 2 users 1. Add 2 users same email Error/warning "อีเมลซ้ำ" P1Neg
TC-39Users > package limit 1. Starter (10 users)
2. Add 11th user
Warning "เกินจำนวนที่แพ็คเก็จรองรับ" · block or upsell P1Bnd
TC-40Delete user 1. Click delete on row Confirm modal · confirm → remove P2Pos

4.6 · 💳 Step 05 · Payment

TC IDScenario StepsExpected Result PriorityType
TC-41Duration stepper · default 1 1. เข้า Step 05 Default 1 month (or 1 year if cycle=yearly) · +/- buttons P1Pos
TC-42Duration + / - 1. Click + 3 times 1 → 2 → 3 → 4 · summary updates · price recalculates P1Pos
TC-43Duration min = 1 1. Duration = 1
2. Click -
- disabled · stays at 1 P2Bnd
TC-44Duration max (upper bound) 1. Type 999 Cap at max (e.g. 60 months / 5 years) · warning P2Bnd
TC-45Duration chip preset 1. Click "6 months" chip Duration = 6 · price = base × 6 P2Pos
TC-46Summary shows all fields 1. View summary Package · cycle · base price · duration · subtotal · VAT 7% · total P1Pos
TC-47Payment tab switch 1. Click Credit → QR → Bank Tabs switch · content changes · active state visible P1Pos
TC-48Credit card · valid 1. Card = "4242 4242 4242 4242"
2. Name = "SOMCHAI"
3. Exp = 12/28
4. CVC = 123
Accept · Omise TEST charge succeeds · loading spinner · advance P1Int
TC-49Credit card · declined 1. Card = "4000 0000 0000 0002" Error "บัตรถูกปฏิเสธ" · retry option · no advance P1Int
TC-50Credit card · invalid CVC 1. CVC = "12" Error "CVC ไม่ถูกต้อง" · client-side P2Neg
TC-51Credit card · expired date 1. Exp = "01/20" Error "บัตรหมดอายุ" · client-side P2Neg
TC-52PromptPay QR display 1. Switch to QR tab QR code image · amount overlay · countdown timer · slip upload area P1Pos
TC-53Slip upload · SlipOK verify 1. Upload valid slip JPG Auto-verify within 30s · success badge · advance P1Int
TC-54Slip upload · invalid amount 1. Upload slip with wrong amount SlipOK returns mismatch · error "ยอดโอนไม่ตรง" · re-upload P1Neg
TC-55Bank transfer · display bank info 1. Switch to Bank tab Kasikorn Bank · HQ · account no · copy button P1Pos
TC-56Bank · copy account no 1. Click copy icon Toast "คัดลอกแล้ว" · clipboard has value P3UI
TC-57Bank slip upload · confirm 1. Upload slip
2. กดยืนยัน
Manual review notice "จะยืนยันภายใน 1 ชั่วโมง" · advance to Welcome P1Pos

4.7 · 🎉 Step 06 · Welcome

TC IDScenario StepsExpected Result PriorityType
TC-58Welcome screen renders 1. Complete payment Congratulations message · order reference · order date · sent-to email count P1Pos
TC-59Invitation emails list 1. View sent-to list Shows all users added in Step 04 · icon per email P2Pos
TC-60Next steps guidance 1. View next-steps section 3-4 step numbered list · "ตรวจสอบอีเมล · ตั้งรหัสผ่าน · เริ่มใช้งาน" P2Pos
TC-61"เริ่มใช้งาน" button 1. Click "เริ่มใช้งาน" Redirect to /dashboard หรือ /login · session created P1Pos

4.8 · 🔒 Trial Lock Behavior

TC IDScenario StepsExpected Result PriorityType
TC-62Trial · duration stepper locked 1. Select Trial
2. Go to Step 05
Duration = 30 days fixed · +/- buttons disabled · locked state visual P1Pos
TC-63Trial · payment section hidden 1. Trial in Step 05 Payment tabs hidden or replaced with trial banner "ฟรี 30 วัน ไม่ต้องชำระเงิน" P1Pos
TC-64Trial · direct advance to Welcome 1. Trial · Step 05
2. Click "เริ่มใช้งาน"
Skip payment · directly advance to Step 06 · no invoice P1Pos

4.9 · 🌐 Language Toggle TH/EN

TC IDScenario StepsExpected Result PriorityType
TC-65TH → EN · full page 1. Click EN flag All labels · stepper · T&C content · package names · buttons update P1Pos
TC-66Preserve data on toggle 1. Fill Step 03
2. Toggle EN
3. Back to TH
Data intact · no reset P1Pos

4.10 · 🔤 Font Size

TC IDScenario StepsExpected Result PriorityType
TC-67A- / A / A+ toggle 1. Click A- · A · A+ Base font-size 13 / 15 / 17 px · all text scales · layout intact P3A11y
TC-68Persist font size choice 1. Choose A+ · refresh Localstorage persist · A+ still active P3UI

4.11 · 🔐 Permission Matrix (view-only)

TC IDScenario StepsExpected Result PriorityType
TC-69Matrix expand/collapse 1. Click phase heading Expand/collapse animation · icon rotate P3UI

4.12 · 🔌 Payment Integration (mock)

TC IDScenario StepsExpected Result PriorityType
TC-70Omise webhook · confirm charge 1. Complete payment
2. Backend receives webhook
Order marked paid · invoice email sent · user activated P1Int

4.13 · 📱 Responsive

TC IDScenario StepsExpected Result PriorityType
TC-71Tablet 768px · stepper horizontal 1. Resize to 768px Stepper switches to horizontal scroll · content full width P3UI

4.14 · ♿ Accessibility

TC IDScenario StepsExpected Result PriorityType
TC-72Keyboard nav · full flow 1. Tab through Step 01→06 Focus visible · logical order · Enter submits · no keyboard traps P2A11y
📊
Test Case Summary: รวม 72 test cases · P1 = 32 · P2 = 30 · P3 = 10 · Positive 34 · Negative 15 · Boundary 5 · Integration 6 · UI 9 · A11y 3

05Playwright Automation Guide

Setup · POM · fixtures · sample tests · CI/CD

🚀 Setup

  1. Install: npm init playwright@latest · TypeScript · Chromium+Firefox+WebKit
  2. Structure: tests/subscribe/, pages/, fixtures/, mocks/
  3. Omise TEST mode: set OMISE_PUBLIC_KEY=pkey_test_xxx · use test cards
  4. SlipOK mock: intercept /api/slipok/verify · return canned success/fail
  5. Email capture: MailHog local · http://localhost:8025 API to read messages

📁 Project Structure

treetests/subscribe/
├── step01-terms.spec.ts
├── step02-package.spec.ts
├── step03-company.spec.ts
├── step04-users.spec.ts
├── step05-payment.spec.ts
├── step06-welcome.spec.ts
├── trial-lock.spec.ts
├── language.spec.ts
├── payment-integration.spec.ts
├── a11y.spec.ts
└── e2e-full-flow.spec.ts
pages/
├── SubscribePage.ts        # Main POM
├── StepPackage.ts
├── StepCompany.ts
├── StepUsers.ts
├── StepPayment.ts
└── StepWelcome.ts
fixtures/
├── companies.json
├── users.json
└── test-cards.json
mocks/
├── omise.mock.ts
├── slipok.mock.ts
└── tambon.mock.ts
playwright.config.ts

🎯 SubscribePage POM

TypeScriptimport { Page, Locator, expect } from '@playwright/test';

export class SubscribePage {
  readonly page: Page;
  readonly stepperItems: Locator;
  readonly btnNext: Locator;
  readonly btnBack: Locator;
  readonly langTh: Locator;
  readonly langEn: Locator;

  constructor(page: Page) {
    this.page = page;
    this.stepperItems = page.locator('.sub-step');
    this.btnNext = page.getByRole('button', { name: /ถัดไป|Next/ });
    this.btnBack = page.getByRole('button', { name: /ย้อนกลับ|Back/ });
    this.langTh = page.locator('[data-lang="th"]');
    this.langEn = page.locator('[data-lang="en"]');
  }

  async goto() { await this.page.goto('/subscribe'); }

  async acceptTerms() {
    await this.page.getByLabel(/ยอมรับข้อกำหนดและเงื่อนไข/).check();
    await this.page.getByLabel(/ยอมรับ.*PDPA/).check();
    await this.btnNext.click();
  }

  async selectPackage(id: 'trial' | 'starter' | 'pro' | 'enterprise') {
    await this.page.locator(`[data-package="${id}"]`).click();
    await this.btnNext.click();
  }

  async setCycle(cycle: 'monthly' | 'yearly') {
    await this.page.locator(`.sub-cycle-toggle button[data-cycle="${cycle}"]`).click();
  }

  async fillCompany(data: {
    name: string; taxId: string;
    contactName: string; contactEmail: string; contactPhone: string;
    tambon: string;
  }) {
    const p = this.page;
    await p.getByLabel(/ชื่อบริษัท/).fill(data.name);
    await p.getByLabel(/เลขประจำตัวผู้เสียภาษี/).fill(data.taxId);
    await p.getByLabel(/ชื่อผู้ติดต่อ/).fill(data.contactName);
    await p.getByLabel(/อีเมลผู้ติดต่อ/).fill(data.contactEmail);
    await p.getByLabel(/เบอร์โทรผู้ติดต่อ/).fill(data.contactPhone);
    await p.getByLabel(/ตำบล/).fill(data.tambon);
    await p.locator('.sub-drop-item').first().click();  // pick 1st autocomplete
    await this.btnNext.click();
  }

  async addUser(u: { name: string; email: string; phone: string; password: string; role: string; }) {
    await this.page.getByRole('button', { name: /เพิ่มผู้ใช้งาน/ }).click();
    const last = this.page.locator('.sub-user-row').last();
    await last.getByLabel(/ชื่อ-นามสกุล/).fill(u.name);
    await last.getByLabel(/อีเมล/).fill(u.email);
    await last.getByLabel(/เบอร์โทร/).fill(u.phone);
    await last.getByLabel(/รหัสผ่าน/).fill(u.password);
    await last.getByLabel(/บทบาท/).selectOption(u.role);
  }

  async payWithCard(card: { number: string; name: string; exp: string; cvc: string; }) {
    const p = this.page;
    await p.getByRole('tab', { name: /บัตรเครดิต/ }).click();
    await p.getByLabel(/เลขบัตร/).fill(card.number);
    await p.getByLabel(/ชื่อบนบัตร/).fill(card.name);
    await p.getByLabel(/วันหมดอายุ/).fill(card.exp);
    await p.getByLabel(/CVC/).fill(card.cvc);
    await p.getByRole('button', { name: /ชำระเงิน|Pay/ }).click();
  }
}

✅ Sample Test · E2E Full Flow (Starter Monthly · Credit Card)

TypeScriptimport { test, expect } from '@playwright/test';
import { SubscribePage } from '../pages/SubscribePage';

test.describe('Subscribe · E2E', () => {
  test('TC-E2E-01 · Starter monthly + credit card · complete signup', async ({ page }) => {
    const sub = new SubscribePage(page);
    await sub.goto();

    // Step 01
    await sub.acceptTerms();

    // Step 02
    await sub.setCycle('monthly');
    await sub.selectPackage('starter');

    // Step 03
    await sub.fillCompany({
      name: 'บ.ธาร์ณัส จำกัด',
      taxId: '0105561234567',
      contactName: 'สมชาย ใจดี',
      contactEmail: 'somchai@tarnas.co.th',
      contactPhone: '0812345678',
      tambon: 'ห้วยขวาง'
    });

    // Step 04
    await sub.addUser({
      name: 'สมชาย ใจดี', email: 'somchai@tarnas.co.th',
      phone: '0812345678', password: 'Str0ng!Pass', role: 'pm'
    });
    await page.getByRole('button', { name: /ถัดไป/ }).click();

    // Step 05 · Payment
    await sub.payWithCard({
      number: '4242424242424242', name: 'SOMCHAI JAIDEE',
      exp: '12/28', cvc: '123'
    });

    // Wait for Welcome
    await expect(page.getByText(/ขอแสดงความยินดี/)).toBeVisible({ timeout: 15000 });
    await expect(page.getByText(/order.*ref/i)).toBeVisible();
  });

  test('TC-62 · Trial lock · duration stepper disabled', async ({ page }) => {
    const sub = new SubscribePage(page);
    await sub.goto();
    await sub.acceptTerms();
    await sub.selectPackage('trial');
    await page.locator('.sub-step:nth-child(5)').click();
    await expect(page.locator('.sub-duration-stepper-lg.is-locked')).toBeVisible();
    await expect(page.locator('.sub-duration-stepper-lg .btn').first()).toBeDisabled();
  });

  test('TC-21 · Tax ID must be 13 digits', async ({ page }) => {
    const sub = new SubscribePage(page);
    await sub.goto();
    await sub.acceptTerms();
    await sub.selectPackage('starter');
    await page.getByLabel(/เลขประจำตัวผู้เสียภาษี/).fill('12345');
    await page.getByRole('button', { name: /ถัดไป/ }).click();
    await expect(page.getByText(/13 หลัก/)).toBeVisible();
  });

  test('TC-65 · TH → EN language toggle', async ({ page }) => {
    const sub = new SubscribePage(page);
    await sub.goto();
    await expect(page.getByText('ข้อกำหนดและเงื่อนไข')).toBeVisible();
    await sub.langEn.click();
    await expect(page.getByText('Terms & Conditions')).toBeVisible();
  });
});

🎭 Mock Setup · Omise + SlipOK

TypeScript · Route Interception// mocks/omise.mock.ts
export async function mockOmise(page: Page) {
  await page.route('**/api.omise.co/**', async route => {
    const body = route.request().postDataJSON();
    if (body?.card?.number?.startsWith('4242')) {
      await route.fulfill({ status: 200, json: {
        id: 'chrg_test_success', status: 'successful', paid: true
      }});
    } else if (body?.card?.number?.startsWith('4000')) {
      await route.fulfill({ status: 400, json: {
        object: 'error', code: 'card_declined', message: 'The card was declined.'
      }});
    }
  });
}

// mocks/slipok.mock.ts
export async function mockSlipOk(page: Page, expectAmount: number) {
  await page.route('**/api/slipok/verify', async route => {
    const body = route.request().postDataJSON();
    if (body.amount === expectAmount) {
      await route.fulfill({ status: 200, json: {
        verified: true, amount: expectAmount, ref: 'SLIP-OK-TEST'
      }});
    } else {
      await route.fulfill({ status: 400, json: {
        verified: false, reason: 'amount_mismatch'
      }});
    }
  });
}
💡
Best Practices: ใช้ getByRole, getByLabel ก่อน CSS · Trial lock ต้องทดสอบทุก paid path · Omise/SlipOK ใช้ mock ใน CI · MailHog ตรวจ invitation emails · Use accessible names (Thai + English fallback ด้วย regex OR)

06Appendix

📌 Version History

  • ConstructQ_Subscribe.html · source · standalone page · 6-step wizard
  • v1.0 · earlier release (task #117)
  • v2.0 · 13 ก.ค. 2569 · Refresh · 72 test cases · 14 areas · restructured · added Trial-lock + Font size + Permission Matrix + Integration coverage

🔗 References

  • Playwright Docs: https://playwright.dev
  • Omise Docs: https://www.omise.co/docs
  • SlipOK Docs: https://slipok.com/documents
  • Test cards (Omise): 4242 4242 4242 4242 (success), 4000 0000 0000 0002 (declined)
  • MailHog: https://github.com/mailhog/MailHog

📊 Excel Companion

ดูไฟล์ ConstructQ_Subscribe_TestCases_v2.0.xlsx คู่กัน · 4 sheets: Summary · Test Scenarios · Test Cases · Playwright Coverage